﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Ants.SimpleDistributionProblem.AntClasses;
using Ants.SimpleDistributionProblem.DTO;

namespace Ants.SimpleDistributionProblem
{
    /// <summary>
    /// Class representing the ant.
    /// </summary>
    public class Ant
    {
        /// <summary>
        /// Gets the solution.
        /// </summary>
        /// <value>
        /// The solution.
        /// </value>
        public Solution Solution { get; private set; }

        /// <summary>
        /// Finds the solution.
        /// </summary>
        /// <param name="routeMap">The route map.</param>
        /// <param name="maxK">The max K.</param>
        /// <param name="alpha">The alpha.</param>
        /// <param name="beta">The beta.</param>
        /// <returns>The solution.</returns>
        public Solution FindSolution(RouteMap routeMap, int maxK, double alpha, double beta)
        {
            this.Solution = new Solution();
            Point currentCity = routeMap.StartingPoint;
            RoutePartWithPropabilityProvider routePartWithPropabilityProvider = new RoutePartWithPropabilityProvider();
            List<Point> visitedCities = new List<Point>();
            Random random = new Random();
            int k = 0;
            while (true)
            {
                if (k < maxK)
                {
                    List<RoutePart> availableRoutes = routeMap.RouteParts.Where(p => (((p.PointA == currentCity) || (p.PointB == currentCity)))).ToList();

                    // If ant is not in storage (startign point) we should not consider routes to the storage.
                    if (currentCity != routeMap.StartingPoint)
                    {
                        availableRoutes = availableRoutes.Where(p => (p.PointA != routeMap.StartingPoint && p.PointB != routeMap.StartingPoint)).ToList();
                    }

                    // Remove routes to visited cities.
                    availableRoutes.RemoveAll(r => visitedCities.IndexOf(r.PointA) != -1 || visitedCities.IndexOf(r.PointB) != -1);

                    if (availableRoutes.Count == 0)
                    {
                        this.Solution.Route.Add(new RoutePart()
                        {
                            PointA = currentCity,
                            PointB = routeMap.StartingPoint
                        });

                        break;
                    }

                    List<RoutePartWithPropability> availableRoutesWithPropability = routePartWithPropabilityProvider.GetRoutePartsWithPropability(availableRoutes, alpha, beta);

                    double randomNumber = random.NextDouble();

                    // Index of randomly choosen city.
                    int index = -1;
                    while (randomNumber > 0 && index < availableRoutesWithPropability.Count - 1)
                    {
                        index++;
                        randomNumber -= availableRoutesWithPropability[index].Probability;
                    }

                    Point visitedCity = new Point();

                    // We have choosen route, and here we determine the coordinates of city to which ant goes. We don't know wheather it's PointA or PointB.
                    if (availableRoutesWithPropability[index].PointA == currentCity)
                    {
                        visitedCity = availableRoutesWithPropability[index].PointB;
                    }
                    else
                    {
                        visitedCity = availableRoutesWithPropability[index].PointA;
                    }

                    // Add step to solution.
                    this.Solution.Route.Add(new RoutePart()
                        {
                            PointA = currentCity,
                            PointB = visitedCity
                        });

                    if (currentCity != routeMap.StartingPoint)
                    {
                        visitedCities.Add(currentCity);
                    }

                    k++;
                    currentCity = visitedCity;
                }
                else
                {
                    // Reset k.
                    k = 0;

                    // Add step to solution.
                    this.Solution.Route.Add(new RoutePart()
                    {
                        PointA = currentCity,
                        PointB = routeMap.StartingPoint
                    });

                    visitedCities.Add(currentCity);
                    currentCity = routeMap.StartingPoint;
                }
            }

            return this.Solution;
        }
    }
}